import Path from "path";
import Fs from "fs-extra";
import { PythonShell } from "python-shell";
import childProcess from "child_process";
import _ from "lodash";

/**
 * runScript函数的参数
 */
export interface RunScriptArg {
  /**
   * 脚本文件绝对路径，可以是.js或者.py
   */
  filePath: string;
  /**
   * 一并拷贝过来的文件
   */
  togetherWith?: {
    /**
     * 一并拷贝过来的文件的路径
     */
    files?: string[];
    /**
     * files的路径为相对还是绝对
     */
    relative?: boolean;
  };
  /**
   * 在哪个文件夹运行该文件(该文件夹会自动创建)
   */
  atDir?: string;
  /**
   * 向该脚本发送的信息,
   * 在Python中 
     def receive():
        sys.stdin = io.TextIOWrapper(sys.stdin.buffer, encoding='utf-8')
        return json.loads(sys.stdin.read())
   * 在Js中, process.on("message", (msgTo)=>{  })
   */
  msgTo?: any;
  /**
   * 该脚本是否为Python，默认自动判断
   */
  isPython?: boolean;
  /**
   * 一块拷贝到atDir目录下的文件或文件夹
   */
  copyFiles?: string[];
  /**
   * 在收到脚本发来的msg之后运行的函数,
   * 对于Python，如果returnJson为true, 该msg已经过JSON.parse
   */
  onMsg?: (msg: any) => any;
  /**
   * 是否在收到msg时就kill这个process；
   * 如果returnJson为true时, 只有收到了nodejs-result时才会kill，
   * 否则只要收到了任意一个msg就会kill
   */
  killOnMsg?: boolean;
  /**
   * 是否对收到的msg进行JSON.parse，(仅用于Python)
   */
  returnJson?: boolean;
  /**
   * 是否在end时Log
   */
  useLog?: boolean;
}
/**
 * 运行一个nodejs或python脚本
 * @example // 在python中接收msgTo
  // def receive():
  //    import sys
  //    import io
  //    import json
  //    sys.stdin = io.TextIOWrapper(sys.stdin.buffer, encoding='utf-8')
  //    return json.loads(sys.stdin.read())
 * @example // 在python中将结果返回给nodejs
  // def returns(result):
  //    import json
  //    print(f"<nodejs-result>{json.dumps(result)}</nodejs-result>")
 * @param option
 * @returns 该脚本的运行结果
 */
export function runScript({
  filePath: originPath,
  togetherWith: { files: env = [], relative = true } = {},
  atDir = Path.dirname(originPath),
  msgTo = [],
  isPython = /py$/i.test(Path.extname(originPath)),
  copyFiles = [],
  onMsg = () => 0,
  killOnMsg = false,
  returnJson = true,
  useLog = false,
}: RunScriptArg): Promise<any> {
  return new Promise((resolve, reject) => {
    const [runOrigin, newPyPath, newEnvPaths, newCopyFilesPaths] = [
      atDir === Path.dirname(originPath), // 是否在Python文件的原位置运行
      Path.resolve(atDir, Path.basename(originPath)), // 新的Python文件位置
      [...env].map((origin) => {
        // 新的Env文件位置
        if (relative) {
          return Path.resolve(atDir, origin);
        } else {
          return Path.resolve(
            atDir,
            Path.relative(Path.dirname(originPath), origin)
          );
        }
      }),
      [...copyFiles].map((e) => Path.resolve(atDir, Path.basename(e))),
    ];
    if (!runOrigin) {
      // 如果不是在原位置运行
      Fs.copySync(originPath, newPyPath, { overwrite: true }); // 把Python原文件拷贝过去
      newEnvPaths.forEach((dest, i) => {
        // 把环境文件拷贝过去
        if (relative) {
          Fs.copySync(Path.resolve(Path.dirname(originPath), env[i]), dest, {
            overwrite: true,
          });
        } else {
          Fs.copySync(env[i], dest, { overwrite: true });
        }
      });
    }
    copyFiles.forEach((e, i) =>
      Fs.copySync(e, newCopyFilesPaths[i], { overwrite: true })
    );
    const removeFiles = () => {
      // 最后删除文件
      if (!runOrigin) {
        Fs.removeSync(newPyPath);
        newEnvPaths.forEach((e) => Fs.removeSync(e));
      }
      newCopyFilesPaths.forEach((e) => Fs.removeSync(e));
    };
    if (isPython) {
      const pyshell = new PythonShell(newPyPath, { mode: "text" });
      pyshell.send(JSON.stringify(msgTo)); // 在Python文件中sys.stdin.read()
      pyshell.on("message", (msg) => {
        // 在Python文件中 print(json.dumps(result))
        if (returnJson) {
          if (/<nodejs-result>.*<\/nodejs-result>/.test(msg)) {
            msg = msg
              .replace(/^<nodejs-result>/g, "")
              .replace(/<\/nodejs-result>$/g, "");
            const result = JSON.parse(msg);
            onMsg(result);
            resolve(result); // 有两个resolve
            killOnMsg && pyshell.kill();
          }
        } else {
          onMsg(msg);
          resolve(msg); // 有两个resolve
          killOnMsg && pyshell.kill();
        }
      });
      pyshell.on("error", (msg) => reject(msg));
      pyshell.stdout.pipe(process.stdout);
      pyshell.stderr.pipe(process.stderr);
      pyshell.end((err, exitCode) => {
        if (useLog) console.log("Python run ended:", { err, exitCode });
        resolve(null);
        removeFiles();
      });
    } else {
      const jsShell = childProcess.fork(newPyPath);
      jsShell.on("message", (msg) => {
        onMsg(msg);
        resolve(msg);
        if (killOnMsg) {
          jsShell.kill();
          if (useLog) {
            console.log("Javascript run ended", { by: "kill" });
          }
          removeFiles();
        }
      });
      jsShell.send(msgTo);
      jsShell.on("error", (err) => reject(err));
      jsShell.on("exit", (code, signal) => {
        if (useLog) {
          console.log("Javascript run ended:", { code, signal });
        }
        resolve(null);
        removeFiles();
      });
    }
  });
}

export function exec(command: string = "pip list") {
  return new Promise((resolve, reject) => {
    const child = childProcess.exec(command, (err, stdout) => {
      err && console.error(err);
      resolve(stdout);
    });
    child.on("error", (err) => reject(err));
  });
}

/**
 * paddleOcr 函数返回值相关内容
 */
export namespace PaddleOcr {
  export interface PosRaw {
    /** x */
    0: number;
    /** y */
    1: number;
  }
  export interface ResultRowRaw {
    /** 该字符串的位置(四个角) */
    0: [PosRaw, PosRaw, PosRaw, PosRaw];
    /** 该字符串的内容和准确度 */
    1: {
      /** 该字符串的内容 */
      0: string;
      /** 该字符串的准确度, 越1越准确 */
      1: number;
    };
  }
  export interface Pos {
    x: number;
    y: number;
  }
  export interface ResultRow {
    /** 该字符串的位置(四个角) */
    pos: [Pos, Pos, Pos, Pos];
    /** 该字符串的内容和准确度 */
    content: {
      /** 该字符串的内容 */
      text: string;
      /** 该字符串的准确度, 越1越准确 */
      accuracy: number;
    };
  }
  export interface OcrResult {
    /** Ocr结果，包括位置和内容 */
    detail: ResultRow[];
    /** Ocr识别出来的文字 */
    text: string;
  }
}

/**
 * 通过直接调用python脚本和paddleOcr来实现OCR文字识别
 * @requires 需要安装Anaconda和paddleOCR
 * @link Anaconda https://www.anaconda.com/
 * @link paddleocr https://pypi.org/project/paddleocr/
 * @param imgPath img文件的绝对路径
 * @param separator 输出text时的分隔符
 * @returns 返回OCR文字识别结果
 */
export async function paddleOcr(
  imgPath: string,
  separator: string = "\n"
): Promise<PaddleOcr.OcrResult> {
  if (!Fs.statSync(imgPath).isFile()) {
    throw new Error("imgPath must be a file");
  } else {
    let detail: PaddleOcr.ResultRowRaw[] = await runScript({
      filePath: Path.resolve(__dirname, "../assets/paddle-ocr.py"),
      msgTo: { imgPath },
      killOnMsg: true,
    });
    let detailParsed: PaddleOcr.ResultRow[] = detail.map((e0) => {
      // @ts-ignore
      const pos: PaddleOcr.ResultRow["pos"] = e0[0].map((e1) => ({
        x: e1[0],
        y: e1[1],
      }));
      const content = {
        text: e0[1][0],
        accuracy: e0[1][1],
      };
      return { pos, content };
    });
    return {
      detail: detailParsed,
      text: detail.map((e) => e[1][0]).join(separator),
    };
  }
}
